; initializes x64 mode, loads off disk kernel into 0x100000, sets stack to 0x200000
; temp PML4 is at 0xa000, PDP at 0xb000, and PDE at 0xc000, only lower 2MB initialized when kernel takes control

; todo: - error codes for error message?
;       - do I NEED to setup an interrupt table / tss for x64 robustness?
;       - a check for memory size would be good
;       - may need to adjust kernel loading code, 128kb may not be enough eventually

org 0x7C00		; where our boot sector will be loaded
	
jmp start		; pass volume information data stored in the VBR sector

volumeFirstBlock   dd 0x01			; first block of volume, should be set by installer
volumeLength       dd 0x00			; total number of blocks in volume, should be set by installer

firstKernelBlock   dd 0x02			; absolute LBA of first sector of kernel, should be set by installer
numKernelBlocks    dw 0x100			; number of kernel blocks to load, currently 128kb

firstPagefileBlock dd 0x00			; abosolute LBA of first sector of page file, should be set by installer
numPagefileBlocks  dd 0x00			; number of blocks to use for page file, should be set by installer

firstServiceTableBlock  dd 0x00  	; absolute LBA of service table, table points to location of service modules
									; in this volume, storage services always point to a seperate primary partition
									; should be set by installer
																	
start:

	; clear segment registers which I think is required for proper offsets
	
	xor ax, ax
	mov ds, ax
	mov es, ax
	
	mov si, ax
	
	; clear screen
	
	mov ah, 0x06
	mov bh, 0x07
	xor cx, cx
	mov dx, 0x184F
	int 0x10
	
	; enable the a20 address line
	
	mov ax, 0x2401
	int 0x15
	jc error
	
	; check for long mode x64
	
    mov eax, 0x80000000
    cpuid
    cmp eax, 0x80000001
    jb error
    
    ; get memory map
    
    mov di, 0x8004		  ; table starts at 0x8004
    xor ebx, ebx
    mov edx, 0x534D4150
    
nextMapEntry:    

	mov dword [di + 20], 1
    mov eax, 0xe820
    mov ecx, 24
    int 0x15
    jc error
    inc si
    test ebx,ebx
    jz   loadKernel
    add  di, 24
    jmp nextMapEntry	; more memory map entries
	
loadKernel:

	xor eax, eax
	mov ax, si
	mov dword [0x8000], eax	; store number of entries at 0x8000
	
	; todo: check memory map for enough memory
	
    ; check for LBA function supported by BIOS
    
	mov ah, 0x41	; int13 ext check
	mov bx, 0x55AA	; test pattern
	mov dl, 0x80	; drive number (80 is first drive, or current drive, or something)
	int 0x13
	jb error
	cmp bx, 0xAA55	; test pattern is reversed on success
	jnz error
	test cl, 0x01	; test for extended read/write
	jz  error
	
	; load loader sectors
	
	mov ax, [numKernelBlocks]
	mov [lbaSectors], ax
	
	mov eax, [firstKernelBlock]
	mov [lbaStartLBA], eax
	
	mov si, lbaPacket	; address of lba packet
	mov ah, 0x42		; ATA ext load LBA function
	mov dl, 0x80		; drive number (starting at 0x80)
	int 0x13			; bios ATA function
	jc error			; if there was any error loading the loader
	
	jmp initProtectedMode    ; else enable protected mode
	
	; print a basic error message
	
error:
	
	mov ah, 0x13	; write text string
	mov al, 0x01	; moving cursor
	xor bh, bh	    ; 0 page
	mov bl, 0x07    ; 7 = white
	mov cx, [errorMsgLength]
	mov bp, errorMsgString
	xor dx, dx
	int 10h
	
spin: jmp spin	; after an error just spin endlessly waiting for ctrl-alt-del

initProtectedMode:
	
	cli	  	    ; disable interrupts
	lgdt [pgdt] ; loads global descriptor table
	
	; enable protected mode
	
	mov eax, cr0
	or  eax, 1
	mov cr0, eax
		
	jmp 0x08:protectedModeStart

bits 32
protectedModeStart:
	
	; setup all non-code segments to use descriptor 10h
		
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov ss, ax
	
	; init long mode
	
	; build page tables
	; the page tables will look like this:
	; PML4:
	; dq 0x000000000000b00b = 00000000 00000000 00000000 00000000 00000000 00000000 10010000 00001111
	; times 511 dq 0x0000000000000000

	; PDP:
	; dq 0x000000000000c00b = 00000000 00000000 00000000 00000000 00000000 00000000 10100000 00001111
	; times 511 dq 0x0000000000000000

	; PD:
	; dq 0x000000000000018b = 00000000 00000000 00000000 00000000 00000000 00000000 00000001 10001111
	; times 511 dq 0x0000000000000000

	; this defines one 2MB page at the start of memory, so we can access the first 2MBs as if paging was disabled

	cld
	mov edi, 0xa000

	mov ax, 0xb00b
	stosw

	xor ax, ax
	mov ecx, 0x07ff
	rep stosw

	mov ax,0xc00b
	stosw

	xor ax,ax
	mov ecx,0x07ff
	rep stosw

	mov ax,0x018b
	stosw

	xor ax,ax
	mov ecx,0x07ff
	rep stosw

	; switch to long mode
	
	sti								; interrupts have to be enabled to enter x64 for some reason
	
	mov eax,10100000b				; set PAE and PGE
	mov cr4,eax

	mov edx, 0x0000a000				; point CR3 at PML4
	mov cr3,edx

	mov ecx,0xC0000080				; specify EFER MSR

	rdmsr						; enable Long Mode
	or eax,0x00000100
	wrmsr
	
	mov ebx,cr0					; activate long mode
	or ebx,0x80000000			; by enabling paging
	mov cr0,ebx
	
	lgdt [gdt.pointer]			; load new 80-bit gdt.pointer below

	jmp 0x08:longModeStart  	; load CS with 64 bit segment and flush the instruction cache

[bits 64]
longModeStart:
	
	cli					; clear interrupts because we're fucked until we setup an IDT if one occurs
	
	mov rsp, 0x200000	; setup stack, todo: find better address
	
	jmp 0x100000		; transfer control to the kernel
	
; data
	
; data

; global descriptor table for long mode (flat, no length specified)

gdt:
dq 0x0000000000000000				; null Descriptor

.code equ $ - gdt
dq 0x0020980000000000                   

.data equ $ - gdt
dq 0x0000900000000000                   

.pointer:
dw $-gdt-1					; 16-bit structure size (limit)
dq gdt						; 64-bit structure address

; global descriptor table for protected mode memory map

pgdt	  dw pgdtEnd - pgdtStart - 1 ; size
          dd pgdtStart               ; address

pgdtStart dd 0  ; null descriptor
          dd 0

		  ; code descriptor
		  
		  dw 0xFFFF ; limit 0:15
		  dw 0      ; base 0:15
		  db 0      ; base 16:23
		  db 0x9A   ; access byte
		  db 0xCF   ; flags, limit 16:19 0CFh
		  db 0      ; base 24:31
		  
		  ; data descriptor
		  
		  dw 0xFFFF ; limit 0:15
		  dw 0      ; base 0:15
		  db 0      ; base 16:23
		  db 0x92   ; access byte
		  db 0xCF   ; flags, limit 16:19 0CFh
		  db 0      ; base 24:31
		 
pgdtEnd   db 0

; error message

errorMsgLength dw 27
errorMsgString db "Error loading system kernel"

; lba packet

lbaPacket   db 0x10   ; size of packet (always 16)
            db 0x00   ; always 0
lbaSectors  dw 0x00   ; number of sectors to transfer
lbaOffset   dw 0x8000 ; offset to transfer to / from
lbaSegment  dw 0xF800 ; segment to transfer to / from, these together are actually 0x100000 with a20 turned on
lbaStartLBA dd 0x00   ; first lba index in transfer
            dd 0x00   ; first lba (high part, only 16 bits used however)  

times 510-($-$$)  db 0       ; 0 padding to magic signature
                  dw 0x0AA55 ; magic mbr signature 
